/*
 * Copyright (c) 2021 Eshel.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.android.basic.components;

import org.jetbrains.annotations.NotNull;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;

/**
 * <br>createBy G
 * <br>createTime: 2021/5/4 10:45
 * <br>desc: 主要用途: 用于描述泛型类
 * <pre>
 *     众所周知, 泛型拥有擦除机制, 本类主要用于描述被擦除的泛型, 以便于进行Json解析。
 *
 *     例如:
 *          String json = "....";
 *          List<String> result;
 *
 *          ParameterizedTypeImpl type = ParameterizedTypeImpl.from(List.class)
 *                 .argument(String.class)
 *                 .build();
 *
 *          result = new Gson().fromJson(json, type);
 *
 *    对于更加复杂的类型, 如:
 *
 *    Map<Map<List<String>, Map<String, List<String>>>, List<String>> map;
 *
 *    可以用以下代码描述:
 *
 *    ParameterizedTypeImpl type = ParameterizedTypeImpl.from(Map.class)
 *                 .argument(Map.class, it -> {
 *                     it.argument(List.class, String.class);
 *
 *                     it.argument(Map.class, map -> {
 *                         map.argument(String.class);
 *                         map.argument(List.class, String.class);
 *                     });
 *                 })
 *                 .argument(List.class, String.class)
 *                 .build();
 * </pre>
 */
public class ParameterizedTypeJavaImpl implements ParameterizedType {

	private final Class<?> rawType;
	private final Type[] actualTypeArguments;
	private Type ownerType;

	private ParameterizedTypeJavaImpl(Type ownerType, Class<?> rawType, Type... actualTypeArguments) {
		this.rawType = rawType;
		this.actualTypeArguments = actualTypeArguments;
		this.ownerType = ownerType;
	}

	@NonNull
	@Override
	public Type[] getActualTypeArguments() {
		return actualTypeArguments;
	}

	@NonNull
	@Override
	public Type getRawType() {
		return rawType;
	}

	@Nullable
	@Override
	public Type getOwnerType() {
		return ownerType;
	}

	@Override
	public int hashCode() {
		return (ownerType == null ? 0 : ownerType.hashCode())
				^ (actualTypeArguments == null ? 0 : Arrays.hashCode(actualTypeArguments)) ^ rawType.hashCode();
	}

	@Override
	public boolean equals(Object other) {
		if (!(other instanceof ParameterizedType)) {
			return false;
		}
		ParameterizedType that = (ParameterizedType) other;
		return getRawType().equals(that.getRawType())
				&& Objects.equals(getOwnerType(), that.getOwnerType())
				&& Arrays.equals(
				getActualTypeArguments(), that.getActualTypeArguments());
	}

	@NotNull
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(toString(rawType));
		if (actualTypeArguments != null && actualTypeArguments.length != 0) {
			sb.append('<');
			for (int i = 0; i < actualTypeArguments.length; i++) {
				if (i != 0) sb.append(", ");
				sb.append(toString(actualTypeArguments[i]));
			}
			sb.append('>');
		}
		return sb.toString();
	}

	private String toString(Type type) {
		if (type instanceof Class) {
			return ((Class<?>) type).getSimpleName();
		}

		return type.toString();
	}

	public static BuilderImpl from(Class<?> rawType) {
		return new BuilderImpl(rawType);
	}

	public static BuilderImpl from(Class<?> rawType, Type ownerType) {
		return new BuilderImpl(ownerType, rawType);
	}

	public static class Builder<T extends Builder<?>> {
		private Builder<?> owner;
		private Class<?> rawType;
		private List<Object> actualTypeArguments = new ArrayList<>();

		private Type ownerType;

		public Builder(Class<?> rawType) {
			this.rawType = rawType;
		}

		public Builder(Type ownerType, Class<?> rawType) {
			this.ownerType = ownerType;
			this.rawType = rawType;
		}

		private T self(){
			//noinspection unchecked
			return (T) this;
		}

		public T argument(Class<?> argumentRawType, Consumer<Builder<Builder<?>>> subArguments) {
			Builder<Builder<?>> builder = new Builder<>(argumentRawType);
			builder.owner = this;
			subArguments.accept(builder);
			actualTypeArguments.add(builder);
			return self();
		}

		public T argument(Class<?> argumentRawType) {
			actualTypeArguments.add(argumentRawType);
			return self();
		}

		public T argument(Class<?> argumentRawType, Class<?> subArgument) {
			actualTypeArguments.add(new ParameterizedTypeJavaImpl(getOwnerRawType(), argumentRawType, subArgument));
			return self();
		}

        /*public Builder getOwner(){
            return owner;
        }*/

		public Type getOwnerRawType() {
			if (owner == null) {
				return ownerType;
			}

			return owner.rawType;
		}

		public Type[] getActualTypeArguments() {
			List<Type> args = new ArrayList<>(actualTypeArguments.size());

			for (Object argument : actualTypeArguments) {
				if (argument instanceof Builder) {
					Builder<?> builder = (Builder<?>) argument;
					args.add(new ParameterizedTypeJavaImpl(builder.getOwnerRawType(), builder.rawType, builder.getActualTypeArguments()));
				} else if (argument instanceof ParameterizedTypeJavaImpl) {
					args.add((Type) argument);
				} else {
					args.add(new ParameterizedTypeJavaImpl(rawType, (Class<?>) argument));
				}
			}

			return args.toArray(new Type[0]);
		}

		protected ParameterizedTypeJavaImpl build() {
			if (actualTypeArguments.isEmpty()) {
				return new ParameterizedTypeJavaImpl(getOwnerRawType(), rawType);
			}
			return new ParameterizedTypeJavaImpl(getOwnerRawType(), rawType, getActualTypeArguments());
		}
	}

	public static class BuilderImpl extends Builder<BuilderImpl> {

		public BuilderImpl(Class<?> rawType) {
			super(rawType);
		}

		public BuilderImpl(Type ownerType, Class<?> rawType) {
			super(ownerType, rawType);
		}

		public ParameterizedTypeJavaImpl build() {
			return super.build();
		}
	}
}